/*
* Author: Chris Seguin
*
* This software has been developed under the copyleft
* rules of the GNU General Public License. Please
* consult the GNU General Public License for more
* details about use and distribution of this software.
*/
package org.acm.seguin.refactor.method;
import java.io.ByteArrayOutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import org.acm.seguin.parser.Node;
import org.acm.seguin.parser.ast.ASTBlockStatement;
import org.acm.seguin.parser.ast.ASTClassBody;
import org.acm.seguin.parser.ast.ASTClassBodyDeclaration;
import org.acm.seguin.parser.ast.ASTClassDeclaration;
import org.acm.seguin.parser.ast.ASTCompilationUnit;
import org.acm.seguin.parser.ast.ASTMethodDeclaration;
import org.acm.seguin.parser.ast.ASTReturnStatement;
import org.acm.seguin.parser.ast.ASTStatement;
import org.acm.seguin.parser.ast.ASTTypeDeclaration;
import org.acm.seguin.parser.ast.ASTUnmodifiedClassDeclaration;
import org.acm.seguin.parser.ast.SimpleNode;
import org.acm.seguin.parser.build.BuildExpression;
import org.acm.seguin.parser.factory.BufferParserFactory;
import org.acm.seguin.parser.query.Found;
import org.acm.seguin.parser.query.Search;
import org.acm.seguin.pretty.ModifierHolder;
import org.acm.seguin.pretty.PrettyPrintVisitor;
import org.acm.seguin.pretty.PrintData;
import org.acm.seguin.refactor.Refactoring;
import org.acm.seguin.refactor.RefactoringException;
import org.acm.seguin.summary.FileSummary;
import org.acm.seguin.summary.SummaryLoadVisitor;
import org.acm.seguin.summary.SummaryLoaderState;
import org.acm.seguin.summary.VariableSummary;
/**
* Refactoring class that extracts a portion of the method and creates a new
* method with what the user has selected.
*
*@author Chris Seguin
*/
public class ExtractMethodRefactoring extends Refactoring
{
private StringBuffer fullFile = null;
private String selection = null;
private String methodName = null;
private SimpleNode root;
private FileSummary mainFileSummary;
private FileSummary extractedMethodFileSummary;
private Node key;
private EMParameterFinder empf = null;
private StringBuffer signature;
/**
* Stores the return type for the extracted method
*/
private Object returnType = null;
private int prot = PRIVATE;
private Object[] arguments = new Object[0];
/**
* The extracted method should be private
*/
public final static int PRIVATE = 0;
/**
* The extracted method should have package scope
*/
public final static int PACKAGE = 1;
/**
* The extracted method should have protected scope
*/
public final static int PROTECTED = 2;
/**
* The extracted method should have public scope
*/
public final static int PUBLIC = 3;
/**
* Constructor for the ExtractMethodRefactoring object
*/
protected ExtractMethodRefactoring()
{
super();
signature = new StringBuffer();
}
/**
* Sets the FullFile attribute of the ExtractMethodRefactoring object
*
*@param value The new FullFile value
*/
public void setFullFile(String value)
{
fullFile = new StringBuffer(value);
}
/**
* Sets the FullFile attribute of the ExtractMethodRefactoring object
*
*@param value The new FullFile value
*/
public void setFullFile(StringBuffer value)
{
fullFile = value;
}
/**
* Sets the Selection attribute of the ExtractMethodRefactoring object
*
*@param value The new Selection value
*/
public void setSelection(String value) throws RefactoringException
{
if (value == null) {
throw new RefactoringException("Nothing has been selected, so nothing can be extracted");
}
selection = value.trim();
if (isStatement())
{
setReturnType(null);
}
else
{
setReturnType("boolean");
}
}
/**
* Sets the MethodName attribute of the ExtractMethodRefactoring object
*
*@param value The new MethodName value
*/
public void setMethodName(String value)
{
methodName = value;
if ((methodName == null) || (methodName.length() == 0))
{
methodName = "extractedMethod";
}
}
/**
* Sets the order of the parameters
*
*@param data The new ParameterOrder value
*/
public void setParameterOrder(Object[] data)
{
empf.setParameterOrder(data);
arguments = data;
}
/**
* Sets the Protection attribute of the ExtractMethodRefactoring object
*
*@param value The new Protection value
*/
public void setProtection(int value)
{
prot = value;
}
/**
* Sets the return type for the extracted method
*
*@param obj The new ReturnType value
*/
public void setReturnType(Object obj)
{
returnType = obj;
}
/**
* Gets the Description attribute of the ExtractMethodRefactoring object
*
*@return The Description value
*/
public String getDescription()
{
return "Extract a method named " + methodName;
}
/**
* Gets the FullFile attribute of the ExtractMethodRefactoring object
*
*@return The FullFile value
*/
public String getFullFile()
{
return fullFile.toString();
}
/**
* Gets the Parameters attribute of the ExtractMethodRefactoring object
*
*@return The Parameters value
*@exception RefactoringException Description of Exception
*/
public VariableSummary[] getParameters() throws RefactoringException
{
preconditions();
Search srch = new Search();
empf = prescan(srch);
LinkedList list = empf.getList();
VariableSummary[] result = new VariableSummary[list.size()];
Iterator iter = list.iterator();
int count = 0;
while (iter.hasNext())
{
result[count] = (VariableSummary) iter.next();
count++;
}
arguments = result;
return result;
}
/**
* Gets the possible return types
*
*@return The return types
*@exception RefactoringException problem in loading these
*/
public Object[] getReturnTypes() throws RefactoringException
{
if (empf == null)
{
return null;
}
return empf.getReturnTypes();
}
/**
* Gets the Statement attribute of the ExtractMethodRefactoring object
*
*@return The Statement value
*/
public boolean isStatement()
{
return (selection.indexOf(";") > 0) || (selection.indexOf("}") > 0);
}
/**
* Gets the Signature attribute of the ExtractMethodRefactoring object
*
*@return The Signature value
*/
public String getSignature()
{
signature.setLength(0);
signature.append(getProtection());
signature.append(" ");
signature.append(getReturnTypeString());
signature.append(" ");
signature.append(methodName);
signature.append("(");
for (int ndx = 0; ndx < arguments.length; ndx++)
{
signature.append(((VariableSummary) arguments[ndx]).getDeclaration());
if (ndx != arguments.length - 1)
{
signature.append(", ");
}
}
signature.append(")");
return signature.toString();
}
/**
* Gets the return type for the extracted method
*
*@return The return type
*/
public Object getReturnType()
{
return returnType;
}
/**
* Gets the ID attribute of the ExtractMethodRefactoring object
*
*@return The ID value
*/
public int getID()
{
return EXTRACT_METHOD;
}
/**
* These items must be true before the refactoring will work
*
*@exception RefactoringException the problem that arose
*/
protected void preconditions() throws RefactoringException
{
if (fullFile == null)
{
throw new RefactoringException("No file specified");
}
if (selection == null)
{
throw new RefactoringException("No selection specified");
}
if (methodName == null)
{
throw new RefactoringException("No method specified");
}
root = getFileRoot();
if (root == null)
{
throw new RefactoringException("Unable to parse the current file.\n" +
"Please make sure you can compile this file before\n" +
"trying to extract a method from it.");
}
mainFileSummary = findVariablesUsed(root);
SimpleNode newMethod = getMethodTree();
if (newMethod == null)
{
throw new RefactoringException("Unable to parse the current selection.\n" +
"Please make sure you have highlighted the entire expression\n" +
"or set of statements.");
}
}
/**
* Actually make the transformation
*/
protected void transform()
{
replaceAllInstances(root);
printFile(root);
}
/**
* Gets the MethodTree attribute of the ExtractMethodRefactoring object
*
*@return The MethodTree value
*/
private SimpleNode getMethodTree()
{
String tempClass = "public class TempClass { " + makeMethod() + "}";
BufferParserFactory bpf = new BufferParserFactory(tempClass);
SimpleNode root = bpf.getAbstractSyntaxTree(false);
extractedMethodFileSummary = findVariablesUsed(root);
ASTTypeDeclaration top = (ASTTypeDeclaration) root.jjtGetChild(0);
ASTClassDeclaration classDecl = (ASTClassDeclaration) top.jjtGetChild(0);
ASTUnmodifiedClassDeclaration unmodifiedClassDecl =
(ASTUnmodifiedClassDeclaration) classDecl.jjtGetChild(0);
ASTClassBody classBody = (ASTClassBody) unmodifiedClassDecl.jjtGetChild(0);
ASTClassBodyDeclaration bodyDecl = (ASTClassBodyDeclaration) classBody.jjtGetChild(0);
return (SimpleNode) bodyDecl.jjtGetChild(0);
}
/**
* Gets the FileRoot attribute of the ExtractMethodRefactoring object
*
*@return The FileRoot value
*/
private SimpleNode getFileRoot()
{
BufferParserFactory bpf = new BufferParserFactory(fullFile.toString());
SimpleNode root = bpf.getAbstractSyntaxTree(true);
return root;
}
/**
* Returns the protection
*
*@return The Protection value
*/
private String getProtection()
{
switch (prot)
{
case PRIVATE:
return "private";
case PACKAGE:
return "";
case PROTECTED:
return "protected";
case PUBLIC:
return "public";
}
return "private";
}
/**
* Returns the return type
*
*@return The ReturnType value
*/
private String getReturnTypeString()
{
if (returnType == null)
{
return "void";
}
else if (returnType instanceof String)
{
return (String) returnType;
}
else if (returnType instanceof VariableSummary)
{
return ((VariableSummary) returnType).getTypeDecl().getName();
}
else
{
return returnType.toString();
}
}
/**
* Replace all instances of code with a selected value
*
*@param root Description of Parameter
*/
private void replaceAllInstances(SimpleNode root)
{
EMBuilder builder = new EMBuilder();
builder.setMethodName(methodName);
builder.setStatement(isStatement());
Search srch = new Search();
if (empf == null)
{
empf = prescan(srch);
}
builder.setParameters(empf.getList());
SimpleNode methodTree = addReturn(getMethodTree());
Found result = srch.search(root, key);
updateModifiers((SimpleNode) result.getRoot(), methodTree);
if (returnType instanceof VariableSummary)
{
builder.setReturnSummary((VariableSummary) returnType);
FindLocalVariableDeclVisitor flvdv = new FindLocalVariableDeclVisitor();
methodTree.jjtAccept(flvdv, returnType);
builder.setLocalVariableNeeded(flvdv.isFound());
}
SimpleNode firstResult = (SimpleNode) result.getRoot();
while (result != null)
{
replaceExtractedMethod(result, builder);
result = srch.search(root, key);
}
insertAtNextClass(firstResult, methodTree);
}
/**
* Prints the file using the pretty printer option
*
*@param root Description of Parameter
*/
private void printFile(SimpleNode root)
{
ByteArrayOutputStream baos;
baos = new ByteArrayOutputStream();
PrintData pd = new PrintData(baos);
PrettyPrintVisitor ppv = new PrettyPrintVisitor();
ppv.visit((ASTCompilationUnit) root, pd);
pd.close();
byte[] buffer = baos.toByteArray();
String file = new String(buffer);
if (file.length() > 0)
{
fullFile = new StringBuffer(file);
}
}
/**
* Creates a string with the new method in it
*
*@return the new method
*/
private String makeMethod()
{
if (isStatement())
{
return getSignature() + "{" + selection + "}";
}
else
{
return getSignature() + "{ return " + selection + "; }";
}
}
/**
* Description of the Method
*
*@param node Description of Parameter
*@return Description of the Returned Value
*/
private FileSummary findVariablesUsed(Node node)
{
if (node == null)
{
return null;
}
SummaryLoaderState state = new SummaryLoaderState();
node.jjtAccept(new SummaryLoadVisitor(), state);
return (FileSummary) state.getCurrentSummary();
}
/**
* Finds the parameters
*
*@param result the location where the section was found
*@return Description of the Returned Value
*/
private EMParameterFinder findParameters(Found result)
{
EMParameterFinder empf = new EMParameterFinder();
empf.setMainFileSummary(mainFileSummary);
empf.setExtractFileSummary(extractedMethodFileSummary);
empf.setLocation(result.getRoot());
empf.run();
return empf;
}
/**
* This allows us to scan for the parameters first
*
*@param srch Description of Parameter
*@return Description of the Returned Value
*/
private EMParameterFinder prescan(Search srch)
{
EMDigger digger = new EMDigger();
if (isStatement())
{
key = digger.last((ASTMethodDeclaration) getMethodTree());
}
else
{
key = digger.dig((ASTMethodDeclaration) getMethodTree());
}
Found result = srch.search(root, key);
EMParameterFinder parameterFinder = findParameters(result);
LinkedList list = parameterFinder.getList();
arguments = new Object[list.size()];
Iterator iter = list.iterator();
int count = 0;
while (iter.hasNext())
{
arguments[count] = iter.next();
count++;
}
return parameterFinder;
}
/**
* Replaces the extracted method
*
*@param result where we found the portion to replace
*@param builder build a method invocation
*/
private void replaceExtractedMethod(Found result, EMBuilder builder)
{
int index = result.getIndex();
int length = key.jjtGetNumChildren();
Node location = result.getRoot();
for (int ndx = 0; ndx < length; ndx++)
{
location.jjtDeleteChild(index);
}
location.jjtInsertChild(builder.build(), index);
}
/**
* Adds the return at the end of the method if one is necessary
*
*@param methodDecl The feature to be added to the Return attribute
*@return Description of the Returned Value
*/
private SimpleNode addReturn(SimpleNode methodDecl)
{
if (returnType instanceof VariableSummary)
{
Node block = methodDecl.jjtGetChild(methodDecl.jjtGetNumChildren() - 1);
ASTBlockStatement blockStatement = new ASTBlockStatement(0);
ASTStatement statement = new ASTStatement(0);
blockStatement.jjtAddChild(statement, 0);
ASTReturnStatement returnStatement = new ASTReturnStatement(0);
statement.jjtAddChild(returnStatement, 0);
BuildExpression be = new BuildExpression();
String name = ((VariableSummary) returnType).getName();
returnStatement.jjtAddChild(be.buildName(name), 0);
block.jjtAddChild(blockStatement, block.jjtGetNumChildren());
}
return methodDecl;
}
/**
* Adds the static and synchronized attributes to the extracted method
*
*@param currentNode where the body of the extracted method was found
*@param methodTree the method we are extracting
*/
private void updateModifiers(SimpleNode currentNode, SimpleNode methodTree)
{
while (!(currentNode instanceof ASTMethodDeclaration))
{
currentNode = (SimpleNode) currentNode.jjtGetParent();
if (currentNode instanceof ASTClassBody)
{
return;
}
}
ASTMethodDeclaration extractedFrom = (ASTMethodDeclaration) currentNode;
ASTMethodDeclaration newMethod = (ASTMethodDeclaration) methodTree;
ModifierHolder efmh = extractedFrom.getModifiers();
ModifierHolder nmmh = newMethod.getModifiers();
nmmh.setStatic(efmh.isStatic());
nmmh.setSynchronized(nmmh.isSynchronized());
}
/**
* Inserts the method at the next class found when heading up from the first
* place where we replaced this value
*
*@param currentNode where this was found
*@param methodTree the method to be inserted
*/
private void insertAtNextClass(SimpleNode currentNode, SimpleNode methodTree)
{
while (!(currentNode instanceof ASTClassBody))
{
currentNode = (SimpleNode) currentNode.jjtGetParent();
if (currentNode == null)
{
return;
}
}
currentNode.jjtInsertChild(methodTree, currentNode.jjtGetNumChildren());
}
}